function SockAddr(aFamily) {
  this._family = aFamily;
}

SockAddr.prototype = {
  get family() {
    return this._family;
  },
  get name() {
    return this._name;
  },
  get addr() {
    return this._addr;
  },
  get port() {
    return this._port;
  }
};

function SockAddrIn(aHost, aPort, aFlag) {
  aFlag = aFlag || Ci.nsIDNSService.RESOLVE_BYPASS_CACHE;
  this._host = aHost;
  this._port = aPort;
  var DNSRecord = DNSService.resolve(aHost, aFlag);
  this._dnsrecord = DNSRecord;
  this._addr = DNSRecord.getNextAddrAsString();
  this._name = this._addr + ":" + aPort;
}

SockAddrIn.prototype = new SockAddr("inet");

function inetStringToAddress(aString) {
  //not implemented yet.
}

function inetAddressToString(aAddress, aProtocol) {
  //not implemented yet.
}

function Socket(aSocketAddr, aTransport) {
  this._address = aSocketAddr;
  this._transport  = aTransport;
  this._istream = null;
  this._ostream = null;
}

Socket.prototype = {
  get address() {
    return this._address;
  },
  get transport() {
    return this._transport;
  },
  get istream() {
    return this._istream;
  },
  get ostream() {
    return this._ostream;
  }
};

function makeClientSocket(aAddressSpec, aHost, aPort, aProxyInfo) {
  switch (aAddressSpec) {
  case "inet":
    return new Socket(new SockAddrIn(aHost, aPort),
                     SocketTransportService.createTransport(null, 0, aHost, aPort, aProxyInfo));
  default:
  }
  return null;
}

function makeServerSocket(aAddressSpec, aPort, aLoopbackOnly, aBackLog) {
  switch (aAddressSpec) {
  case "inet":
    var serverSocket = Cc["@mozilla.org/network/server-socket;1"]
		                     .createInstance(Ci.nsIServerSocket);
    serverSocket.init(aPort, aLoopbackOnly, aBackLog || -1);
    return new Socket(new SockAddrIn("localhost", aPort), serverSocket);
  default:
  }
  return null;
}

//aSegmentSize
//If OPEN_UNBUFFERED is not set, then this parameter specifies the size of each buffer segment (pass 0 to use default value).
//aSegmentCount
//If OPEN_UNBUFFERED is not set, then this parameter specifies the maximum number of buffer segments (pass 0 to use default value).

function socketInputStream(aSocket, aOpt) {
  if (aSocket.istream) return aSocket.istream;
  aOpt = aOpt || {};
  aOpt.buffering = 0; //aOpt.buffering || Ci.nsITransport.OPEN_BLOCKING;
  aOpt.sink = aOpt.sink || false;

  var istream = aSocket.transport.openInputStream(aOpt.buffering, 0, 0);

  var bistream = Cc["@mozilla.org/binaryinputstream;1"].
                   createInstance(Ci.nsIBinaryInputStream);
  bistream.setInputStream(istream);

  var cistream = Cc["@mozilla.org/intl/converter-input-stream;1"].
                   createInstance(Ci.nsIConverterInputStream);
  cistream.QueryInterface(Ci.nsIUnicharLineInputStream);
  cistream.init(istream, "UTF-8", 0, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);

  var pump = Cc["@mozilla.org/network/input-stream-pump;1"].
               createInstance(Ci.nsIInputStreamPump);
  pump.init(istream, -1, -1, 0, 0, false);

  aSocket._istream = new InputStream(kSTREAM_TYPE_FILE, "socket input", istream, bistream, cistream, null, pump);

  aSocket._istream.buf = "";
  aSocket._istream.wait = false;
  aSocket._istream.wait_finish = true;
  aSocket.sink = aOpt.sink;
  var listener = {
    socket: aSocket,
    onStartRequest: function (channel, socketContext) {},
    onStopRequest: function (channel, socketContext, status, errorMsg) {
      this.socket._istream._cistream.close();
      this.socket._istream._bistream.close();
      this.socket._istream.wait        = false;
      this.socket._istream.wait_finish = false;
    },
    onDataAvailable: function onDataAvailableSocketInputStream(channel, socketContext, inputStream, sourceOffset, count) {
      if (this.sink) {
        //TODO consider to seek to sourceOffset.
        //this.sink._ostream = this.sink.storage.getOutputStream(sourceOffset);
        this.sink.ostream.writeFrom(inputStream, count);
        this.sink.ostream.flush();
      } else {
        var str = {};
        this.socket._istream._cistream.readString(count, str);
        this.socket._istream.buf += str.value;
      }
      this.socket._istream.wait = false;
    }
  };
  pump.asyncRead(listener, null);
  return aSocket._istream;
}

function socketOutputStream(aSocket, aOpt) {
  if (aSocket.ostream) return aSocket.ostream;
  aOpt = aOpt || {};
  aOpt.buffering = aOpt.buffering || Ci.nsITransport.OPEN_BLOCKING;
  var ostream = aSocket.transport.openOutputStream(aOpt.buffering, 0, 0);

  var bostream = Cc["@mozilla.org/binaryoutputstream;1"].
                   createInstance(Ci.nsIBinaryOutputStream);
  bostream.setOutputStream(ostream);

  var costream = Cc["@mozilla.org/intl/converter-output-stream;1"].
                   createInstance(Ci.nsIConverterOutputStream);
  costream.init(ostream, "UTF-8", 0, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);

  aSocket._ostream = new OutputStream(kSTREAM_TYPE_FILE, "socket output", ostream, bostream, costream);
  return aSocket._ostream;
}

function socketClose(aSocket) {
  if (aSocket.istream) closeInputStream (aSocket.istream);
  if (aSocket.ostream) closeOutputStream(aSocket.ostream);
  aSocket.transport.close(null);
}

function readLineSync(aInputStream) {
  for (;;) {
    if (aInputStream.buf.length) {
      var m = aInputStream.buf.match(/(.*)$/m);
      if (m) {
        aInputStream.buf = aInputStream.buf.substr(aInputStream.buf.search(/\n/) + 1);
        return m[1];
      }
    }
    aInputStream.wait = true;
    while (aInputStream.wait) {
      ThreadManager.mainThread.processNextEvent(true);
    }
  }
}

function waitFinish(aInputStream) {
  while (aInputStream.wait_finish) {
    ThreadManager.mainThread.processNextEvent(true);
  }
}